package org.hawk.gwt.ppc.introspection.impl;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.hawk.gwt.ppc.introspection.JavaCallableMember;
import org.hawk.gwt.ppc.introspection.JavaGenericTypeScope;
import org.hawk.gwt.ppc.introspection.JavaTypeScope;
import org.hawk.gwt.ppc.introspection.JavaTypeVariable;
import org.hawk.gwt.ppc.parser.CompilationUnitDescriptor;
import org.hawk.gwt.ppc.parser.Descriptor;
import org.hawk.gwt.ppc.parser.ParameterizedMemberDescriptor;
import org.hawk.gwt.ppc.parser.VariableDescriptor;

/**
 * Base class for introspection callable members: constructors and methods.
 * 
 * @author alex.bereznevatiy@gmail.com
 *
 */
abstract class AbstractJavaCallableMemberScope<T extends Descriptor & ParameterizedMemberDescriptor> 
		extends JavaScopeImpl implements JavaCallableMember {
	
	public AbstractJavaCallableMemberScope() {
		super();
	}

	/**
	 * @param buildGeneric - build generic parameters if true.
	 * @return resolved parameter types from source descriptor
	 */
	List<JavaTypeScope> getSourceParametersTypes(boolean buildGeneric) {
		T descriptor = getDescriptor();
		List<VariableDescriptor> vars = descriptor.getParameters();
		List<JavaTypeScope> result = createParametersList(vars.size());
		for (VariableDescriptor var : vars) {
			if (buildGeneric) {
				result.add(GenericHelper.buildGenericType(var.getType(), this));
			} else {
				JavaTypeScope scope = resolveType(var.getType(), getCompilationUnit());
				if (scope instanceof JavaGenericTypeScope) {
					result.add(((JavaGenericTypeScope)scope).getRawType()); 
				} else {
					result.add(scope);
				}
			}
		}
		return Collections.unmodifiableList(result);
	}

	/**
	 * Useful method to modify parameters for certain types.
	 * @param size
	 * @return list of parameters ready to insert explicit ones.
	 */
	List<JavaTypeScope> createParametersList(int size) {
		return new ArrayList<JavaTypeScope>(size);
	}

	/**
	 * @param params
	 * @return resolved parameter types from binary reflection type
	 */
	List<JavaTypeScope> getBinaryParametersTypes(Type[] params) {
		List<JavaTypeScope> result = new ArrayList<JavaTypeScope>(params.length);
		for (Type param : params) {
			result.add(GenericHelper.buildGenericType(param, this));
		}
		return Collections.unmodifiableList(result);
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.JavaCallableMember#getSignature()
	 */
	public String getSignature() {
		StringBuilder result = new StringBuilder();
		result.append(getName()).append('(');
		boolean first = true;
		for(JavaTypeScope param : getParametersTypes()){
			if(first)first=false;
			else result.append(',');
			result.append(param.getName());
		}
		result.append(')');
		return result.toString();
	}

	/**
	 * @return name of this callable member
	 */
	abstract String getName();

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.impl.JavaScopeImpl#getDescriptor()
	 */
	@SuppressWarnings("unchecked")
	@Override
	T getDescriptor() {
		return (T)super.getDescriptor();
	}

	/* (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.impl.JavaScopeImpl#resolveTypeParameter(java.lang.String)
	 */
	@Override
	JavaTypeVariable resolveTypeParameter(String paramName) {
		for (JavaTypeVariable var : getTypeVariables()) {
			if (var.getName().equals(paramName)) {
				return var;
			}
		}
		return super.resolveTypeParameter(paramName);
	}

	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.introspection.impl.JavaScopeImpl#resolveType(java.lang.String, org.hawk.gwt.ppc.parser.CompilationUnitDescriptor)
	 */
	@Override
	JavaTypeScope resolveType(String name, CompilationUnitDescriptor context) {
		if (name.endsWith("[]")) {
			JavaTypeScope element = resolveType(name.substring(0, name.length() - 2), context);
			return element instanceof JavaGenericTypeScope ? 
					new JavaArrayGenericTypeScopeImpl(element, this) : 
					new JavaArrayTypeScopeImpl(element);
		}
		for (JavaTypeVariable typeVar : getTypeVariables()) {
			if (typeVar.getName().equals(name)) {
				return typeVar;
			}
		}
		return super.resolveType(name, context);
	}

	/*
	 * (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return getSignature();
	}
}